# -*- coding: utf-8 -*-
""" XML parsing module. Not for production! """
__all__ = ['xml_parser']
__author__ = 'Sergey Sobko, Timir Nureev'
__copyright__ = 'Copyright (c) 2010, DEKO PROFIT (The ProfitWare Project)'
__license__ = 'BSD'
__credits__ = ['Sergey Sobko', 'Timir Nureev']
__email__ = 'info@dekoprofit.ru'
__status__ = 'Development'

import ProfitDCS.ml.sgml as sgml
from ProfitDCS import ch
from logging import warning as w

use_memcache = False

try:
	import memcache
	mc = memcache.Client(['127.0.0.1:11211'], debug=0)
	mc.set('__memcache_test', 'true')
	assert mc.get('__memcache_test') == 'true'
	import cPickle
	from ProfitDCS.security import md5sum
	use_memcache = True
except Exception:
	w('ProfitDCS:ml / Not using memcached')
	pass

class dict_node(dict):
	""" XML Node -- dict() object """
	__parent = None
	__children = tuple()
	children, parent, name, attr = \
		[lambda self:self.__children,lambda self:self.__parent,lambda self:self.keys()[0],lambda self,id=None:id is None and self.values()[0] or (self.values()[0].has_key(id) and self.values()[0][id] or None)] 
	def addchild(self, content = {}):
		child = dict_node(content)
		child.__setparent(self)
		self.__children += (child,)
		return child
	def __setparent(self, parent):
		self.__parent = parent
	
class xml_parser:
	""" XML Parser - use one to parse your XML """
	__xml = []
	__xmd5 = None
	__deleted = []
	__root = None
	__xml_walk=lambda self,xml,cn:xml.values()[0]['__tagtype']==0 and cn.addchild(xml) or xml.values()[0]['__tagtype']==1 and (cn.addchild(xml) and cn) or cn.parent()	
	def __init__(self, str, um = False):
		""" str - string XML representation """
		global use_memcache
		use_memcache = use_memcache and um
		try:
			if use_memcache:
				s = md5sum(str)
				self.__xmd5 = s
				x = mc.get(s)
				if x is not None:
					self.__xml, self.__root = x
					return
			self.__xml = sgml.parse_sgml(str)
			current_node = self.__root = dict_node(self.__xml[0])
			for i in range(1, len(self.__xml)):
				current_node = self.__xml_walk(self.__xml[i], current_node)
			if use_memcache:
				mc.set(s, (self.__xml, self.__root))
		except Exception, e:
			w('ProfitDCS:ml / XML parsing error: %s' % e)
			pass
	
	def set_element(self, path, content = {}):
		all = self.get_element(path, True)
		if len(all) > 0 and len(content) > 0:
			for i in all:
				i.values()[0].update(content)
	
	def add_element(self, path, tag, attr):
		attr.update({'__tagtype': 1, '__aftertext': ''})
		nel = {tag: attr}
		el = self.get_element(path, True)[0]
		idx = self.get_nodenum(el) + 1
		el.addchild(nel)
		self.__xml.insert(idx, nel)
	
	def del_element(self, path):
		for el in self.get_element(path, True):
			self.__deleted += [el]
			idx = self.get_nodenum(el)
			if el.attr('__tagtype') == 1:
				del self.__xml[idx]
				del el
			else:
				tname = el.keys()[0]
				for i in range(idx, len(self.__xml)):
					if self.__xml[i].keys()[0] == tname and self.__xml[i].values()[0]['__tagtype'] == 2:
						idx2 = i
						break
				del self.__xml[idx:idx2 + 1]
				
	def get_nodenum(self, node):
		return self.__xml.index(node)
	
	def __str__(self):
		ret = '<?xml version="1.0" encoding="UTF-8"?>\n'
		lvl, postlvl = 0, 0
		for i in self.__xml:
			attr = i.values()[0]
			if not attr.has_key('__aftertext'):
				attr['__aftertext'] = ''
			else:
				attr['__aftertext'] = ch('\t', '', attr['__aftertext'])
			pre, post, lvl, postlvl = [('', '', lvl, 1), ('', ' /', lvl, 0), ('/', '', lvl - 1, 0)][attr['__tagtype']]
			tattr = ''
			for t in attr:
				if t in ('__tagtype', '__aftertext'):
					continue		
				tattr += ' %(attr)s="%(value)s"' % {'attr': t, 'value': attr[t]}
			ret += '%(lvl)s<%(pre)s%(tag)s%(tattr)s%(post)s>%(aftertext)s\n' % {'tag': i.keys()[0], 'pre': pre, 'tattr': tattr, 'post': post, 'aftertext': attr['__aftertext'], 'lvl': '\t' * lvl}
			lvl += postlvl
		return ret
	
	__repr__ = lambda self: str(self.__xml)
	
	def get_element(self, path = '', needtaginfo = False):
		""" path in form '/root/node1[id=test]/node2[id=.{4}]/hello' """
		if use_memcache:
			s = md5sum(self.__xmd5 + path)
			x = mc.get(s)
			if x is not None:
				return x
		pth = sgml.xml_path(path)
		def nodes_gen():
			next_nodes = (self.__root,)
			level = 0
			for i, key, value in pth:
				current_nodes = next_nodes
				next_nodes = ()
				level += 1
				for node in current_nodes:
					if node.keys()[0] == i and (key == value == '' or node.values()[0].has_key(key) or value == 'default'):
						if value == 'default':
							if not node.values()[0].has_key(key) or node.values()[0][key] == value:
								next_nodes += node.children()
								yield level, node  
								continue						
						if key != '':
							if (value.isalnum() and value != node.values()[0][key]) or len(sgml.find(value, node.values()[0][key])) == 0:
								continue
						next_nodes += node.children()
						yield level, node
		nodelist = ()
		for level, node in nodes_gen():
			if node in self.__deleted:
				continue
			if level == len(pth):
				if not needtaginfo:
					nnode = dict_node({node.keys()[0]: dict([(x, node.attr()[x]) for x in filter(lambda x: x not in ['__tagtype', '__aftertext'], node.attr())])})
				nodelist += (needtaginfo and node or nnode,)
		if use_memcache:
			mc.set(s, nodelist)
		return nodelist
